<?php
// $Id$

/**
 * @file
 * 
 */

/**
 * The import URL for the Battle.net (HTML) guild roster.
 *
 * Contains the following placeholders, in this order:
 * - Region
 * - Language
 * - Server name
 * - Guild name
 *
 * @see wow_get_bnet_locale()
 */
define('WOW_BNET_HTML_ROSTER_URL', 'http://%s.battle.net/wow/%s/guild/%s/%s/roster');
/**
 * The Battle.net realm status API location.
 * 
 * @see http://us.battle.net/wow/en/forum/topic/2369741469
 */
define('WOW_BNET_API_REALM_STATUS_URL', 'http://%s.battle.net/api/wow/realm/status');
/**
 * The import URL for Wowhead's XML item data.
 *
 * Contains the following placeholders, in this order:
 * - Region ('www': english, 'de', 'es', 'fr', 'ru')
 * - Item ID number
 */
define('WOW_ITEM_IMPORT_URL', 'http://%s.wowhead.com/item=%d&xml');
/**
 * The WoWProgress guild ranking API location.
 * 
 * Contains the following placeholders, in this order:
 * - Region (Only 'us' and 'eu' regions are available.)
 * - Server name
 * - Guild name
 *
 * @see http://www.wowprogress.com/post/37_Data_Export
 */
define('WOW_WOWPROGRESS_API_RANKING_URL', 'http://www.wowprogress.com/guild/%s/%s/%s/json_rank');
/**
 * The GuildOx guild ranking API location.
 *
 * Contains the following placeholders, in this order:
 * - Guild name
 * - Server name
 * - Region ('us', 'eu', 'kr' or 'tw')
 *
 * @see http://www.guildox.com/go/g.asp?w=AB&a=1
 */
define('WOW_GUILDOX_API_RANKING_URL', 'http://www.guildox.com/go/guildxml.aspx?n=%s&r=%s-%s&j=1');

define('WOW_BNET_ITEM_LINK', 'http://us.battle.net/wow/en/item/%d');
define('WOW_WOWHEAD_ITEM_LINK', 'http://%s.wowhead.com/item=%d');
define('WOW_THOTTBOT_ITEM_LINK', 'http://thottbot.com/item=%d');
define('WOW_MMOCHAMPION_ITEM_LINK', 'http://db.mmo-champion.com/i/%d');

/**
 * -----------------------------------------------------------------------------
 * REGION: Common import functions.
 * -----------------------------------------------------------------------------
 */

/**
 * Loads an XML document into a SimpleXML object.
 *
 * @param $data The XML data to load. This is not done here as
 *   SimpleXMLElement($url, NULL, TRUE) will hang and/or cause a server error
 *   if the connection cannot be established.
 * @param &$xml The variable to save the SimpleXMLElement object to.
 *
 * @return If the XML was successfully parsed TRUE, otherwise FALSE.
 */
function wow_import_load_xml($data, &$xml) {
  $xml = NULL;
  
  // Surpress all errors while loading the XML document.
  $use_errors = libxml_use_internal_errors(TRUE);
  
  // See drupal_xml_parser_create() for an alternative?
  try {
    $xml = new SimpleXMLElement($data);
  }
  catch (Exception $e) {
    watchdog('wow', t('XML parsing failed: %error.'), array('%error' => $e->getMessage()), WATCHDOG_WARNING);
  }
  
  // Clear the error log and reset error handling to the previous value.
  libxml_clear_errors();
  libxml_use_internal_errors($use_errors);
  
  return isset($xml) ? TRUE : FALSE;
}

/**
 * Loads a HTML document into a DOMDocument object.
 *
 * @param $data Raw HTML data obtained from a HTTP request.
 *
 * @return A DOMDocument object of the provided HTML data.
 */
function wow_import_load_html($data) {
  $html = new DOMDocument();
  //$html->preserveWhiteSpace = FALSE;
  
  // Make sure you shut this method up or you'll get warnings all over the place.
  $result = @$html->loadHTML($data);
  if ($result == FALSE) {
    return NULL;
  }
  
  return $html;
}

/**
 * Utility function used with importing data from Battle.net.
 * 
 * Gets the Battle.net locale that should be used for the site or verifies the
 * locale provided. If a region or language is not supported by the armory,
 * valid default values will be returned.
 *
 * @param $region (optional) The Battle.net armory region to use. This should be
 *   'us', 'eu' or 'ko'.
 * @param $langcode (optional) The Battle.net language to use. If not provided,
 *   this function will attempt to use the default site language. Valid
 *   languages for each armory region are:
 *   - US: en, es
 *   - EU: de, en, es, fr, ru
 *   - KR: ko
 *
 * @return An array containing a valid region and language to use in a Battle.net URL.
 */
function wow_get_bnet_locale($region = '', $langcode = '') {
  if (empty($region)) {
    $region = variable_get('wow_armory_region', 'us');
  }
  
  if (empty($langcode)) {
    $langcode = language_default('language');
  }
  
  switch ($region) {
    case 'us':
      $bnet_langcode = in_array($langcode, array('en', 'es')) ? $langcode : 'en';
      break;
    case 'eu':
      $bnet_langcode = in_array($langcode, array('de', 'en', 'es', 'fr', 'ru')) ? $langcode : 'en';
      break;
    case 'kr':
      $bnet_langcode = 'ko';
      break;
    default:
      $region = 'us';
      $bnet_langcode = 'en';
      break;
  }
  
  return array('region' => $region, 'language' => $bnet_langcode);
}

/**
 * -----------------------------------------------------------------------------
 * REGION: Item import functions.
 * -----------------------------------------------------------------------------
 */

/**
 * Extracts an item's name and rarity from Wowhead's XML data.
 *
 * @param $data XML data retrieved from Wowhead.
 *
 * @return An array containing the item information or NULL if an error occured.
 */
function wow_import_parse_item($data) {
  $xml = NULL;
  
  if (!wow_import_load_xml($data, $xml)) {
    drupal_set_message(t('An error occured while processing XML data.'), 'error');
    return NULL;
  }
  
  if (isset($xml->error)) {
    drupal_set_message('Item not found. The item ID number or link may be incorrect.', 'error');
    return NULL;
  }
  
  $item['name'] = (string)$xml->item->name;
  $item['rarity'] = (int)$xml->item->quality['id'];
  
  //$jsonStr = (string)$xml->item->json;
  //$jsonStr = '{' . $jsonStr .'}';
  //$json = json_decode($jsonStr, TRUE);
  //if (isset($json['heroic']) && $json['heroic'] == TRUE) {
  //  $item['description'] = t('Heroic');
  //}
  
  return $item;
}

/**
 * Retrieves an item's name, rarity and link from Wowhead.
 *
 * @param $item_id The ID number of an in-game item.
 */
function wow_import_item($item_id) {
  global $language;
  $region = in_array($language->language, array('de', 'es', 'fr', 'ru')) ? $language->language : 'www';
  
  // Create a link that uses the power script provider that was selected.
  $power_script = variable_get('wow_power_script', WOW_SCRIPT_NONE);
  switch ($power_script) {
    case WOW_SCRIPT_WOWHEAD:
      $link = sprintf(WOW_WOWHEAD_ITEM_LINK, $region, $item_id);
      break;
    case WOW_SCRIPT_THOTTBOT:
      $link = sprintf(WOW_THOTTBOT_ITEM_LINK, $item_id);
      break;
    case WOW_SCRIPT_MMOCHAMPION:
      $link = sprintf(WOW_MMOCHAMPION_ITEM_LINK, $item_id);
      break;
    default:
      // @todo This could also be set to a Battle.net armory link once localization code is implemented.
      $link = '';
      break;
  }
  
  $item = NULL;
  $url = sprintf(WOW_ITEM_IMPORT_URL, $region, $item_id);
  
  // Get the item data from Wowhead.
  $response = drupal_http_request($url);
  
  switch ($response->code) {
    case 200: // OK
    case 302: // Found
    case 307: // Temporary Redirect
      $item = wow_import_parse_item($response->data);
      break;
    case 500: // Internal Server Error
    case 503: // Service Unavailable
      drupal_set_message(t('The Wowhead server is temporarily unavailable or too busy. Please try again later.'), 'error');
      break;
    default:
      drupal_set_message(t('An error occured while retrieving item information.'), 'error');
      watchdog('wow', 'Could not retrieve data from Wowhead. Request response: %error.',
        array('%error' => '['. $response->code . '] '. $response->error), WATCHDOG_WARNING);
      break;
  }
  
  // Save the new item if nothing bad happened.
  if (isset($item)) {
    // Add the required guild module fields (and the link) to the item array.
    $item += array('item_op' => 'add', 'description' => '', 'module'=> 'guild', 'type' => 'item', 'link' => $link);
    
    // @todo Check if the item already exists.
    
    module_load_include('inc', 'guild', 'includes/guild.items');
    guild_save_item($item);
    
    drupal_set_message(t('The item %name has been successfully imported.', array('%name' => $item['name'])));
  }
}

/**
 * -----------------------------------------------------------------------------
 * REGION: Battle.net armory import functions (HTML).
 * -----------------------------------------------------------------------------
 */

/**
 * Extracts a guild roster from an armory page.
 *
 * @param $data An HTML document obtained from the Battle.net armory.
 *
 * @return An array containing character information of a guild's members.
 */
function wow_import_parse_roster_html($data) {
  $html = NULL;
  
  $html = wow_import_load_html($data);
  if (!isset($html)) {
    return NULL;
  }
  
  $xpath = new DOMXPath($html);
  
  //$serverError = $xpath->query("//div[@id='server-error']");
  
  $roster = array();
  $rosterNodeList = $xpath->query("//div[@id='roster']/table/tbody/tr");
  
  foreach ($rosterNodeList as $characterNode) {
    $player = array();
    
    $player['name']    = utf8_decode($xpath->query("td[@class='name']", $characterNode)->item(0)->nodeValue);
    $player['raceId']  = $xpath->query("td[@class='race']", $characterNode)->item(0)->getAttribute('data-raw');
    $player['classId'] = $xpath->query("td[@class='cls']", $characterNode)->item(0)->getAttribute('data-raw');
    $player['level']   = $xpath->query("td[@class='lvl']", $characterNode)->item(0)->nodeValue;
    $player['achvPts'] = $xpath->query("td[@class='ach-points']", $characterNode)->item(0)->nodeValue;
    
    $rankElement = $xpath->query("td[@class='rank']", $characterNode)->item(0);
    $player['rankId']   = $rankElement->getAttribute('data-raw');
    $player['rankName'] = utf8_decode($rankElement->nodeValue);
    
    $roster[] = $player;
  }
  
  return $roster;
}

/**
 * Retieves information on all the characters in a World of Warcraft guild from
 * the Battle.net armory.
 *
 * @param $server The server the guild is on.
 * @param $name The guild name.
 * @param $region (optional) The armory region to use.
 * @param $language (optional) The armory language to use.
 *
 * @see wow_get_bnet_locale()
 */
function wow_import_bnet_html_roster($server, $name, $region = 'us', $language = 'en') {
  $url = sprintf(WOW_BNET_HTML_ROSTER_URL, $region, $language, $server, $name);
  
  $roster = NULL;
  $response = drupal_http_request($url);
  
  switch ($response->code) {
    case 200: // OK
    case 302: // Found
    case 307: // Temporary Redirect
      $roster = wow_import_parse_roster_html($response->data);
      break;
  //case 304: // Not Modified
  //  break;
    case 404: // Not Found
      $t_args = array('!settings-link' => url('admin/guild/settings'));
      drupal_set_message(t('The guild could not be found. ' .
        'Make sure that the <a href="!settings-link">game server, guild name, and armory region settings</a> are correct.'), 'error');
      break;
    case 500: // Internal Server Error
    case 503: // Service Unavailable
      drupal_set_message(t('The Battle.net server is temporarily unavailable or too busy. Please try again later.'), 'error');
      break;
    default:
      drupal_set_message(t('An error occured while retrieving Battle.net armory data.'), 'error');
      watchdog('wow', 'Could not retrieve data from the Battle.net armory. Request response: %error.',
        array('%error' => '['. $response->code . '] '. $response->error), WATCHDOG_WARNING);
      break;
  }
  
  return $roster;
}

/**
 * -----------------------------------------------------------------------------
 * REGION: Battle.net API functions.
 *
 * NOTICE: At this time the World of Warcraft Community Platform API is only
 *   available as a preview release. As such, all APIs are a work in progress
 *   and subject to change at any time. The following code may become
 *   incompatible with future changes and is only provided for proof of
 *   concept purposes only.
 * -----------------------------------------------------------------------------
 */

/**
 *
 */
function wow_import_bnet_realm_status($realms = array(), $region = 'us') {
  $url = sprintf(WOW_BNET_API_REALM_STATUS_URL, $region);
  
  if (!empty($realms)) {
    $realms = is_string($realms) ? array($realms) : $realms;
    $realms = array_map('rawurlencode', $realms);
    $url .= '?realm=' . implode('&realm=', $realms);
  }
  
  $realm_status = NULL;
  $response = drupal_http_request($url);
  dpm($url);dpm($response);
  // @todo We can use this api to alter the guild realm field on the guild
  //   settings page. An autocomplete textfield or dropdown would work and
  //   could be verified.
  
  switch ($response->code) {
    case 200: // OK
    case 302: // Found
    case 307: // Temporary Redirect
      $data = json_decode($response->data, TRUE);
      
      // Preferably if a realm can't be found the server would instead give us a
      // HTTP 200 code and either return an empty array or set the 'status'
      // field to 'nok'. Then we could just check the field's presence with
      // if (!isset($data['status']) {} or if (!empty($data['realms']) {}
      
      $realm_status = $data['realms'];
      break;
  //case 304: // Not Modified
  //  break;
    case 404: // Not Found
      // Reason: "When in doubt, blow it up. (page not found)"
      
      drupal_set_message(t('Battle.net API function not found.'), 'error');
      watchdog('wow', 'The API at %url does not exist.', array('%url' => $url), WATCHDOG_ERROR);
      break;
    case 500: // Internal Server Error
      // Reason: "Invalid realm list"
      
      // If the API server can't return status information (when we feed it
      // non-existant realm names) it *really* shouldn't return a HTTP 500 code.
      drupal_set_message(t('The realm could not be found.'), 'error');
      break;
  //case 503: // Service Unavailable
  //  break;
    default:
      drupal_set_message(t('An error occured while retrieving Battle.net data.'), 'error');
      watchdog('wow', 'Could not retrieve data from the Battle.net server. Request response: [@code] %error.',
        array('@code' => $response->code, '%error' => $response->error), WATCHDOG_WARNING);
      break;
  }
  dpm($realm_status);
  return $realm_status;
}

/**
 * -----------------------------------------------------------------------------
 * REGION: Raid progression import functions.
 * -----------------------------------------------------------------------------
 */

/**
 * @todo Not implemented.
 */
function wow_import_wowprogress_rankings($name, $server, $region = 'us') {
  // @todo Use drupal_query_string_encode() for Battle.net API functions?
  $url = sprintf(WOW_WOWPROGRESS_API_RANKING_URL, $region, rawurlencode($server), rawurlencode($name));
  
  // WoWProgress only outputs a 'Last-Modified' header for their files; it saves
  // us the trouble of having to implement 'If-Modified-Since' negotiation for
  // simple JSON data.
  $response = drupal_http_request($url);
  $data = NULL;
  
  switch ($response->code) {
    case 200:  // OK
    case 302:  // Found
    case 307:  // Temporary Redirect
      $data = json_decode($response->data, TRUE);
      
      // WoWProgress returns a 200 code for like everything...
      if (empty($data)) {
        $t_args = array('!settings-link' => url('admin/guild/settings'));
        drupal_set_message(t('WoWProgress could not find the guild or realm name. ' .
          'Make sure that the <a href="!settings-link">game server and guild name settings</a> are correct.', $t_args), 'error');
      }
      break;
    default:
      drupal_set_message(t('An error occured while retrieving data from WoWProgress.'), 'error');
      watchdog('wow', 'Could not retrieve data from the WoWProgress server. Request reponse: [@code] %error.',
        array('@code' => $response->code, '%error' => $response->error), WATCHDOG_WARNING);
      break;
  }
  
  return $data;
}

/**
 * @todo Not implemented.
 */
function wow_import_guildox_rankings() {
  
}
